@@ -1,3 +1,5 @@ |
||
1 |
+require 'location' |
|
2 |
+ |
|
1 | 3 |
# Events are how Huginn Agents communicate and log information about the world. Events can be emitted and received by |
2 | 4 |
# Agents. They contain a serialized `payload` of arbitrary JSON data, as well as optional `lat`, `lng`, and `expires_at` |
3 | 5 |
# fields. |
@@ -33,10 +35,10 @@ class Event < ActiveRecord::Base |
||
33 | 35 |
} |
34 | 36 |
|
35 | 37 |
def location |
36 |
- @location ||= { |
|
37 |
- # lat and lng are BigDecimal, so convert them to Float |
|
38 |
- lat: (lat.to_f if lat), |
|
39 |
- lng: (lng.to_f if lng), |
|
38 |
+ @location ||= Location.new( |
|
39 |
+ # lat and lng are BigDecimal, but converted to Float by the Location class |
|
40 |
+ lat: lat, |
|
41 |
+ lng: lng, |
|
40 | 42 |
radius: |
41 | 43 |
begin |
42 | 44 |
h = payload[:horizontal_accuracy].presence |
@@ -47,21 +49,8 @@ class Event < ActiveRecord::Base |
||
47 | 49 |
(h || v || payload[:accuracy]).to_f |
48 | 50 |
end |
49 | 51 |
end, |
50 |
- course: |
|
51 |
- begin |
|
52 |
- if (v = payload[:course].presence) && |
|
53 |
- (v = v.to_f) >= 0 |
|
54 |
- v |
|
55 |
- end |
|
56 |
- end, |
|
57 |
- speed: |
|
58 |
- begin |
|
59 |
- if (v = payload[:speed].presence) && |
|
60 |
- (v = v.to_f) >= 0 |
|
61 |
- v |
|
62 |
- end |
|
63 |
- end, |
|
64 |
- } |
|
52 |
+ course: payload[:course], |
|
53 |
+ speed: payload[:speed].presence) |
|
65 | 54 |
end |
66 | 55 |
|
67 | 56 |
# Emit this event again, as a new Event. |
@@ -0,0 +1,82 @@ |
||
1 |
+Location = Struct.new(:lat, :lng, :radius, :speed, :course) |
|
2 |
+ |
|
3 |
+class Location |
|
4 |
+ protected :[]= |
|
5 |
+ |
|
6 |
+ def initialize(data = {}) |
|
7 |
+ super() |
|
8 |
+ |
|
9 |
+ case data |
|
10 |
+ when Array |
|
11 |
+ raise ArgumentError, 'unsupported location data' unless data.size == 2 |
|
12 |
+ self.lat, self.lng = data |
|
13 |
+ when Hash, Location |
|
14 |
+ data.each { |key, value| |
|
15 |
+ begin |
|
16 |
+ __send__("#{key}=", value) |
|
17 |
+ rescue NameError |
|
18 |
+ end |
|
19 |
+ } |
|
20 |
+ else |
|
21 |
+ raise ArgumentError, 'unsupported location data' |
|
22 |
+ end |
|
23 |
+ |
|
24 |
+ yield self if block_given? |
|
25 |
+ end |
|
26 |
+ |
|
27 |
+ def lat=(value) |
|
28 |
+ self[:lat] = floatify(value) { |f| |
|
29 |
+ if f.abs <= 90 |
|
30 |
+ f |
|
31 |
+ else |
|
32 |
+ raise ArgumentError, 'out of bounds' |
|
33 |
+ end |
|
34 |
+ } |
|
35 |
+ end |
|
36 |
+ |
|
37 |
+ def lng=(value) |
|
38 |
+ self[:lng] = floatify(value) { |f| |
|
39 |
+ if f.abs <= 180 |
|
40 |
+ f |
|
41 |
+ else |
|
42 |
+ raise ArgumentError, 'out of bounds' |
|
43 |
+ end |
|
44 |
+ } |
|
45 |
+ end |
|
46 |
+ |
|
47 |
+ def radius=(value) |
|
48 |
+ self[:radius] = floatify(value) { |f| f if f >= 0 } |
|
49 |
+ end |
|
50 |
+ |
|
51 |
+ def speed=(value) |
|
52 |
+ self[:speed] = floatify(value) { |f| f if f >= 0 } |
|
53 |
+ end |
|
54 |
+ |
|
55 |
+ def course=(value) |
|
56 |
+ self[:course] = floatify(value) { |f| f if (0..360).cover?(f) } |
|
57 |
+ end |
|
58 |
+ |
|
59 |
+ def present? |
|
60 |
+ lat && lng |
|
61 |
+ end |
|
62 |
+ |
|
63 |
+ def empty? |
|
64 |
+ !present? |
|
65 |
+ end |
|
66 |
+ |
|
67 |
+ private |
|
68 |
+ |
|
69 |
+ def floatify(value) |
|
70 |
+ case value |
|
71 |
+ when nil, '' |
|
72 |
+ return nil |
|
73 |
+ else |
|
74 |
+ float = Float(value) |
|
75 |
+ if block_given? |
|
76 |
+ yield(float) |
|
77 |
+ else |
|
78 |
+ float |
|
79 |
+ end |
|
80 |
+ end |
|
81 |
+ end |
|
82 |
+end |
@@ -0,0 +1,49 @@ |
||
1 |
+require 'spec_helper' |
|
2 |
+ |
|
3 |
+describe Location do |
|
4 |
+ let(:location) { |
|
5 |
+ Location.new( |
|
6 |
+ lat: BigDecimal.new('2.0'), |
|
7 |
+ lng: BigDecimal.new('3.0'), |
|
8 |
+ radius: 300, |
|
9 |
+ speed: 2, |
|
10 |
+ course: 30) |
|
11 |
+ } |
|
12 |
+ |
|
13 |
+ it "converts values to Float" do |
|
14 |
+ expect(location.lat).to equal 2.0 |
|
15 |
+ expect(location.lng).to equal 3.0 |
|
16 |
+ expect(location.radius).to equal 300.0 |
|
17 |
+ expect(location.speed).to equal 2.0 |
|
18 |
+ expect(location.course).to equal 30.0 |
|
19 |
+ end |
|
20 |
+ |
|
21 |
+ it "provides hash-style access to its properties with both symbol and string keys" do |
|
22 |
+ expect(location[:lat]).to equal 2.0 |
|
23 |
+ expect(location['lat']).to equal 2.0 |
|
24 |
+ end |
|
25 |
+ |
|
26 |
+ it "does not allow hash-style assignment" do |
|
27 |
+ expect { |
|
28 |
+ location[:lat] = 2.0 |
|
29 |
+ }.to raise_error |
|
30 |
+ end |
|
31 |
+ |
|
32 |
+ it "ignores invalid values" do |
|
33 |
+ location2 = Location.new( |
|
34 |
+ lat: 2, |
|
35 |
+ lng: 3, |
|
36 |
+ radius: -1, |
|
37 |
+ speed: -1, |
|
38 |
+ course: -1) |
|
39 |
+ expect(location2.radius).to be_nil |
|
40 |
+ expect(location2.speed).to be_nil |
|
41 |
+ expect(location2.course).to be_nil |
|
42 |
+ end |
|
43 |
+ |
|
44 |
+ it "considers a location empty if either latitude or longitude is missing" do |
|
45 |
+ expect(Location.new.empty?).to be_truthy |
|
46 |
+ expect(Location.new(lat: 2, radius: 1).present?).to be_falsy |
|
47 |
+ expect(Location.new(lng: 3, radius: 1).present?).to be_falsy |
|
48 |
+ end |
|
49 |
+end |
@@ -18,13 +18,12 @@ describe Event do |
||
18 | 18 |
describe "#location" do |
19 | 19 |
it "returns a default hash when an event does not have a location" do |
20 | 20 |
event = events(:bob_website_agent_event) |
21 |
- event.location.should == { |
|
21 |
+ event.location.should == Location.new( |
|
22 | 22 |
lat: nil, |
23 | 23 |
lng: nil, |
24 | 24 |
radius: 0.0, |
25 | 25 |
speed: nil, |
26 |
- course: nil, |
|
27 |
- } |
|
26 |
+ course: nil) |
|
28 | 27 |
end |
29 | 28 |
|
30 | 29 |
it "returns a hash containing location information" do |
@@ -37,31 +36,12 @@ describe Event do |
||
37 | 36 |
course: 90.0, |
38 | 37 |
} |
39 | 38 |
event.save! |
40 |
- event.location.should == { |
|
39 |
+ event.location.should == Location.new( |
|
41 | 40 |
lat: 2.0, |
42 | 41 |
lng: 3.0, |
43 | 42 |
radius: 0.0, |
44 | 43 |
speed: 0.5, |
45 |
- course: 90.0, |
|
46 |
- } |
|
47 |
- end |
|
48 |
- |
|
49 |
- it "ignores invalid speed and course" do |
|
50 |
- event = events(:bob_website_agent_event) |
|
51 |
- event.lat = 2 |
|
52 |
- event.lng = 3 |
|
53 |
- event.payload = { |
|
54 |
- speed: -1, |
|
55 |
- course: -1, |
|
56 |
- } |
|
57 |
- event.save! |
|
58 |
- event.location.should == { |
|
59 |
- lat: 2.0, |
|
60 |
- lng: 3.0, |
|
61 |
- radius: 0.0, |
|
62 |
- speed: nil, |
|
63 |
- course: nil, |
|
64 |
- } |
|
44 |
+ course: 90.0) |
|
65 | 45 |
end |
66 | 46 |
end |
67 | 47 |
|